package com.bankcard.bininfo.loader;

import com.bankcard.bininfo.model.BinInfo;
import com.bankcard.bininfo.BinInfoRepository;

import java.io.InputStream;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.function.Supplier;

public class BinInfoLoader {
    private static final String CSV_BIN_INFO_FILE = "bin_info.csv";
    private static final String JSON_BIN_INFO_FILE = "bin_info.json";
    private static final int THREAD_POOL_SIZE = Runtime.getRuntime().availableProcessors();

    private static final BinInfoLoadStrategy csvStrategy = new CsvBinInfoLoadStrategy();
    private static final BinInfoLoadStrategy jsonStrategy = new JsonBinInfoLoadStrategy();
    private static final ExecutorService executorService = Executors.newFixedThreadPool(THREAD_POOL_SIZE);

    private BinInfoLoader() {
        // Private constructor to prevent instantiation
    }

    public static void loadInitialBinInfo(boolean useJson, String binInfoFilePath) {
        String resourceFile = (binInfoFilePath == null || binInfoFilePath.isEmpty())
                ? (useJson ? JSON_BIN_INFO_FILE : CSV_BIN_INFO_FILE)
                : binInfoFilePath;
        BinInfoLoadStrategy strategy = useJson ? jsonStrategy : csvStrategy;

        try (InputStream is = BinInfoLoader.class.getResourceAsStream(resourceFile)) {
            if (is == null) {
                throw new RuntimeException("Unable to find " + resourceFile);
            }

            List<BinInfo> binInfoList = strategy.loadBinInfo(is);
            loadBinInfoConcurrently(binInfoList);
        } catch (Exception e) {
            throw new RuntimeException("Failed to load initial BIN information", e);
        }
    }

    public static void loadIncrementalBinInfo(InputStream inputStream, boolean isJson) {
        BinInfoLoadStrategy strategy = isJson ? jsonStrategy : csvStrategy;
        try {
            List<BinInfo> binInfoList = strategy.loadBinInfo(inputStream);
            loadBinInfoConcurrently(binInfoList);
        } catch (Exception e) {
            throw new RuntimeException("Failed to load incremental BIN information", e);
        }
    }

    public static void loadIncrementalBinInfo(String data) {
        boolean isJson = data.trim().startsWith("{") || data.trim().startsWith("[");
        BinInfoLoadStrategy strategy = isJson ? jsonStrategy : csvStrategy;
        try {
            List<BinInfo> binInfoList = strategy.loadBinInfo(new java.io.ByteArrayInputStream(data.getBytes()));
            loadBinInfoConcurrently(binInfoList);
        } catch (Exception e) {
            throw new RuntimeException("Failed to load incremental BIN information from string", e);
        }
    }

    public static void loadBinInfo(Supplier<List<BinInfo>> binInfoSupplier) {
        try {
            loadBinInfoConcurrently(binInfoSupplier.get());
        } catch (Exception e) {
            throw new RuntimeException("Failed to load incremental BIN information", e);
        }
    }

    private static void loadBinInfoConcurrently(List<BinInfo> binInfoList) {
        binInfoList.forEach(binInfo ->
                executorService.submit(() -> BinInfoRepository.getInstance().addBinInfo(binInfo))
        );
    }

    public static void shutdown() {
        executorService.shutdown();
    }
}